package nachos.threads;
import java.util.LinkedList;
import nachos.ag.BoatGrader;
import nachos.machine.Lib;

public class Boat
{
    static BoatGrader bg;

    static final int OAHU = 0;
    static final int MOLOKAI = 1;
    static final int OCEAN = 2;

    private static int adultsOnOahu, childrenOnOahu, childrenAtStart;
    private static int boatLocation;
    private static boolean activeRower;

    private static Lock boatLock;
    private static Condition2 waitingIslanders, rowersWaitingForRiders,
    ridersWaitingToBeRowed;

    private static Communicator endCom;
    
    static private class Islander extends KThread {
        int place;
        Islander (Runnable target) {
            super(target);
            place = OAHU;
        }
    }
    
    static private Islander I() {
        return (Islander) KThread.currentThread();
    }

    public static void begin( int adults, int children, BoatGrader b ) {
        // Store the externally generated autograder in a class
        // variable to be accessible by children.
        bg = b;

        // Instantiate global variables here
        adultsOnOahu = adults;
        childrenOnOahu = children;
        childrenAtStart = children;

        boatLocation = OAHU;
        activeRower = false;

        boatLock = new Lock();
        endCom = new Communicator();
        waitingIslanders = new Condition2 (boatLock);
        rowersWaitingForRiders = new Condition2 (boatLock);
        ridersWaitingToBeRowed = new Condition2 (boatLock);

        // Create threads here
        boatLock.acquire();
        LinkedList<KThread> threads = new LinkedList<KThread>();
        for (int k=0; k<children; k++) {
            Runnable r = new Runnable() {
                public void run() {
                    while (true)
                        ChildItinerary();
                }
            };
            KThread t = new Islander(r);
            t.setName("child"+k);
            threads.add(t);
        }
        for (int k=0; k<adults; k++) {
            Runnable r = new Runnable() {
                public void run() {
                    while (true)
                        AdultItinerary();
                }
            };
            KThread t = new Islander(r);
            t.setName("adult"+k);
            threads.add(t);
        }
        // the next code randomizes the forking process
        while (threads.size() > 0) {
            int rand = (int) (Math.random() * threads.size());
            threads.remove(rand).fork();
        }
        boatLock.release();
        
        // wait until solution reached
        int endResult = endCom.listen();
        if (Lib.test('m'))
            if (endResult == 0)
                System.out.println("success!!!");
            else
                System.out.println("OHNO!!! FAILURE!!!");
    }

    static void AdultItinerary() {
        boatLock.acquire();
        
        // I only care if I'm on Oahu
        if (I().place == OAHU) {
            
            // sleep if the boat's not here or if there aren't kids on Molokai
            while (boatLocation != OAHU || childrenOnOahu == childrenAtStart) {
                waitingIslanders.wake();
                waitingIslanders.sleep();
            }
            
            // just row to Molokai
            I().place = MOLOKAI;
            bg.AdultRowToMolokai();
            adultsOnOahu--;
            boatLocation = MOLOKAI;
            waitingIslanders.wake();
        }
        
        checkFinishCondition();
        boatLock.release();
    }

    static void ChildItinerary() {
        boatLock.acquire();
        
        // am I on Oahu or Molokai?
        if (I().place == OAHU) {
            // I am at Oahu
            
            // wait for the boat
            while (boatLocation != OAHU) {
                waitingIslanders.wake();
                waitingIslanders.sleep();
            }
            
            // the boat is here!
            // is there a rower?
            if (activeRower) {
                // there is a rower, therefore I'm the rider
                // wake the waiting rower & push off into the ocean
                rowersWaitingForRiders.wake();
                boatLocation = OCEAN;
                // wait until the rower has completed the trip
                ridersWaitingToBeRowed.sleep();
                // at this point, I've been rowed!
                I().place = MOLOKAI;
                // it is the rower's responsibility to move the boat & wake ppl
            }
            
            // there is not a rower yet
            // are there any children left on Oahu?
            else if (childrenOnOahu > 1) {
                // become the rower
                activeRower = true;
                // wake someone else to ride with & wait for them
                waitingIslanders.wake();
                rowersWaitingForRiders.sleep();
                // rider found, now complete the trip
                ridersWaitingToBeRowed.wake();
                childrenOnOahu -= 2;
                I().place = MOLOKAI;
                boatLocation = MOLOKAI;
                waitingIslanders.wake();
                bg.ChildRowToMolokai();
                bg.ChildRideToMolokai();
                activeRower = false;
            }
            
            // I am the only child left on Oahu
            else {
                bg.ChildRowToMolokai();
                childrenOnOahu--;
                boatLocation = MOLOKAI;
                I().place = MOLOKAI;
                waitingIslanders.wake();
            }
            
            
        }
        
        // I'm on Molokai
        else {
            
            while (I().place != boatLocation) {
                waitingIslanders.wake();
                waitingIslanders.sleep();
            }
            
            // the boat is here, therefore i'll take it back
            bg.ChildRowToOahu();
            childrenOnOahu++;
            boatLocation = OAHU;
            I().place = OAHU;
            waitingIslanders.wake();
        }
        
        checkFinishCondition();
        boatLock.release();
    }

    static private void checkFinishCondition() {
        if (Lib.test('m'))
            System.out.println("("+childrenOnOahu+", "+adultsOnOahu+
                    ", "+(childrenAtStart-childrenOnOahu)+")");
        if (childrenOnOahu == 0 && adultsOnOahu == 0)
            endCom.speak(0);
    }

    public static void selfTest() {
        BoatGrader b = new BoatGrader();

        System.out.println("\n ***Testing Boats with only 2 children***");
        begin(4, 2, b);

        // begin can only be called once at a time!!!
    }
}
